package uniandes.caing.ccambios.entidades;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.io.*;
import java.nio.file.DirectoryIteratorException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

/**
 * 
 * @author Alvaro Olmos
 * @version 1.0.0.0
 * @since 30 mar 2012 Descripci�n: Clase encargada de comparar dos programas
 *        fuente
 * 
 */
public class SourceComparator {
	// Datos de cambios
	private Source source1; // Programa fuente original
	private Source source2; // Programa fuente cambiado

	public Source getSource1() {
		return source1;
	}

	public Source getSource2() {
		return source2;
	}

	/**
	 * Descripci�n: constructor de la clase, recibe los dos programas a
	 * comparar.
	 * 
	 * @param Fuente1
	 * @param Fuente2
	 * @throws IOException
	 */
	public SourceComparator(Source Source1, Source Source2) throws Exception {
		// Asignar las fuentes
		source1 = Source1;
		source2 = Source2;
		// Iniciar la comparaci�n
		CompareSources();
	}

	/**
	 * Descripci�n: compara los fuentes y genera los datos de cambios
	 */

	protected void CompareSources() throws Exception {
		// Recorrer cada archivo del Fuente2
		String filePathRoot1 = source1.getRootPath();
		for (SourceFile file2 : source2.getFiles()) {
			// Buscar el respectivo archivo en el Fuente1 (misma ruta relativa)
			String filePathSufijo = file2.getFilePath().substring(
					filePathRoot1.length());
			String filePath1 = filePathRoot1 + filePathSufijo;
			// filePath1 = filePath1.replace("_CC", "");
			SourceFile file1Existente = null;
			for (SourceFile file1 : source1.getFiles()) {
				if (file1.getFilePath().replace("_CC", "").equals(filePath1)) {
					file1Existente = file1;
					file2.setCounterPart(file1);
					file1.setCounterPart(file2);
					break;
				}
			}
			if (file1Existente != null) {
				// Si se encuentra hacer la comparaci�n de ambos y marcarlo como
				// comparado
				file1Existente.setCompared(true);
				CompareFiles(file1Existente, file2);
			} else {
				// Si no se encuentra se marca como borrado
				file2.setDeleted(true);
			}
		}
		// Los archivos no marcados del Fuente1 se marcan como borrados
		for (SourceFile file1 : source1.getFiles()) {
			if (file1.getCompared() == false) {
				file1.setDeleted(true);
			}
		}
	}

	/**
	 * Descripci�n: compara dos archivos y les asigna los valores de comparaci�n
	 * Prerrequisitos: los archivos deben estar limpios, sin l�neas vac�as y sin
	 * comentarios, las l�neas deben llegar numeradas seg�n su posici�n original
	 * 
	 * @param File1
	 * @param File2
	 */
	protected void CompareFiles(SourceFile File1, SourceFile File2) {
		int numBorradas = 0;
		int numAdicionadas = 0;
		int numTotal = 0;
		int idxBusqueda = 0;
		ArrayList<Line> lineas1 = File1.getLines();
		Line[] lineas2 = File2.getLines().toArray(new Line[0]);
		ArrayList<Line> borradas = new ArrayList<Line>();

		// Recorrer las l�neas del File1
		for (int j = 0; j < lineas1.size(); j++) {
			Line l1 = lineas1.get(j);
			boolean encontrado = false;
			// Tomar la l�nea n del File1 y empezar a buscarla desde la n del
			// File2
			for (int i = idxBusqueda; i < lineas2.length; i++) {
				Line l2 = lineas2[i];
				if (l2.getSourceCodeLine().equals(l1.getSourceCodeLine())) {
					for (int k = idxBusqueda; k < i; k++) {
						lineas2[k].setLineChangeLabel(new LineChangeLabel(File2
								.getVersion(), ChangeLineType.Added, l1
								.getHistoricalData()));
						numAdicionadas += 1;
					}
					encontrado = true;
					l1.setLineChangeLabel(new LineChangeLabel(File1
							.getVersion(), ChangeLineType.Unchanged, l1
							.getHistoricalData()));
					l2.setLineChangeLabel(new LineChangeLabel(File2
							.getVersion(), ChangeLineType.Unchanged, l1
							.getHistoricalData()));
					idxBusqueda = i + 1;
					break;
				}
			}
			// Si no se encuentra la l�nea, marcarla como borrada
			if (!encontrado) {

				// validar por que se establece la linea 1 con cambios
				l1.setLineChangeLabel(new LineChangeLabel(File1.getVersion(),
						ChangeLineType.Removed, ""));
				l1.setNumber(l1.getNumber() + numAdicionadas);
				numBorradas += 1;
				borradas.add(l1);
			}
		}
		// Las l�neas no marcadas al final del File2 son nuevas
		for (int i = idxBusqueda; i < lineas2.length; i++) {
			if (lineas2[i].getLineChangeLabel() == null
					|| lineas2[i].getLineChangeLabel().equals(
							ChangeLineType.None)) {
				lineas2[i].setLineChangeLabel(new LineChangeLabel(File2
						.getVersion(), ChangeLineType.Added, ""));
			}
		}

		// Unir a lineas2 las l�neas borradas de lineas1
		Line[] res = new Line[lineas2.length + borradas.size()];
		System.arraycopy(lineas2, 0, res, 0, lineas2.length);
		System.arraycopy(borradas.toArray(), 0, res, lineas2.length,
				borradas.size());
		sortLines(res);

		// Reasignar el listado completo de l�neas
		File2.getLines().clear();
		for (Line l : res) {
			File2.getLines().add(l);
		}
		numTotal = File2.getLines().size();
		ChangeHeader ch = new ChangeHeader(source2.getVersion(),
				source2.getVersionChangeType(), new Date(), numAdicionadas,
				numBorradas, numTotal, source2.getversionAuthor(), 0, "",
				File2.getName());
		File2.addChangeHeader(ch);
		createChangesVersion(source2);
	}

	private static void sortLines(Line[] list) {
		for (int i = 0; i < list.length; i++) {
			Line linea = list[i];
			int j = i - 1;
			while (j >= 0 && list[j].getNumber() > linea.getNumber()) {
				list[j + 1] = list[j];
				j -= 1;
			}
			list[j + 1] = linea;
		}
	}

	@SuppressWarnings("unused")
	private static SourceFile loadHistoryChangesVersion(SourceFile source1) {

		return null;
	}

	/**
	 * Crea archivos la estructura base de directorios, para establecer el nuevo
	 * codigo fuente con etiquetas
	 * 
	 * @param File1
	 * @param File2
	 */
	private static String createDirectories(boolean rutaInicial,
			String sourcePath, String newRelativePath) {

		Path dir = Paths.get(sourcePath);
		String directoryRoot = "";

		if (rutaInicial) {
			newRelativePath = "";
			String[] baseRootArray = sourcePath.split("\\\\");

			for (int i = 0; i < baseRootArray.length - 1; i++) {

				newRelativePath = newRelativePath + baseRootArray[i] + "\\";

			}

			directoryRoot = newRelativePath
					+ baseRootArray[baseRootArray.length - 1];
			newRelativePath = newRelativePath
					+ baseRootArray[baseRootArray.length - 1] + "_CC";

			File newRootPath = new File(newRelativePath);
			newRootPath.mkdir();

		}

		try {
			DirectoryStream<Path> stream = Files.newDirectoryStream(dir);
			for (Path file1 : stream) {

				if ((new File(file1.toString())).isDirectory()) {

					String[] array = file1.toString().split("\\\\");
					File newDirectory = new File(newRelativePath + "\\"
							+ array[array.length - 1]);
					newDirectory.mkdir();

					createDirectories(false, file1.toString(),
							newDirectory.toString());

				}

			}

		} catch (IOException x) {
			System.err.println(x);
		} catch (DirectoryIteratorException x) {
			// IOException can never be thrown by the iteration.
			// In this snippet, it can only be thrown by newDirectoryStream.
			System.err.println(x);
		}
		return directoryRoot;

	}

	/**
	 * Realiza la creaci�n de una version de cambios de acuerdo a una version
	 * especificada
	 * 
	 * @param File1
	 * @param File2
	 */
	private static void createChangesVersion(Source source) {

		String directoryRoot = createDirectories(true, source.getRootPath(), "");

		for (int i = 0; i < source.getFiles().size(); i++) {

			SourceFile sf = source.getFiles().get(i);
			String sourceFilePath = sf.getFilePath().replace(directoryRoot,
					directoryRoot + "_CC");

			try {
				File outFile = new File(sourceFilePath);
				// si no existe creo la estructura de directorios
				if (!outFile.exists()) {
					String parentDir = sourceFilePath.replace(sf.getName(), "");
					File dir = new File(parentDir);
					dir.mkdirs();
					outFile.createNewFile();
				}
				BufferedWriter writer = new BufferedWriter(new FileWriter(
						outFile));

				writer.write(sf.getChangeHeadersPrint());
				writer.newLine();
				for (int j = 0; j < sf.getLines().size(); j++) {

					Line l = sf.getLines().get(j);

					if (!l.getLineChangeLabelsPrint().equals("")) {

						writer.write(l.getLineChangeLabelsPrint());

						if (!l.getLineChangeLabel().getChangeLineType()
								.equals(ChangeLineType.Removed)) {

							writer.newLine();
						}

					}

					if (!l.getSourceCodeLine().equals("")) {

						if (l.getLineChangeLabel().getChangeLineType()
								.equals(ChangeLineType.Removed)) {

							writer.write("//" + l.getSourceCodeLine());
							writer.newLine();

						} else {

							writer.write(l.getSourceCodeLine());
							writer.newLine();
						}

					}

				}

				writer.close();

			} catch (IOException e) {
				System.err.println(e);
				System.exit(1);
			}

		}

	}
}
